#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "isa.h"

unsigned char *mem;		// main memory of simulator
unsigned char reg1, reg2;		// used to install reg id

/* 
 * Func: get_reg_id
 * @reg: a byte data to install regs
 * @index: reg number of regs, must be 1 or 2
 * return: reg id
 */
char get_reg_id(char reg, int index){
  switch (index) {
  case 1:
    return reg >> 4;
  case 2:
    return reg & 0x0F;
  default:
    return 0xFF;
  }
}

/*
 * Func: cpu, execute instruction
 * @pc: program counter
 */
void cpu(int pc){
  int cycle = 0;	// number of instruction
  char mem_content[25];	// translate memory to string

  while(cycle++ < 20){
    switch(mem[pc]){
    case 0x0:		// halt
      printf("\nsystem halt\n");
      return;
    case 0x10:		// nop
      pc++;
      continue;
    case 0x20:		// rrmov %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value = reg_table[reg1].value;
      pc += 2;
      break;
    case 0x30:		// irmov imme, %reg
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value =(int) mem[pc+2];
      pc += 6;
      break;
    case 0x40:		// store %reg1, D(%reg2)
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      mem[reg_table[reg2].value + *(int *)mem[pc+2]] = reg_table[reg1].value;
      pc += 6;
      break;
    case 0x50:		// load D(%reg1), %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value = mem[reg_table[reg1].value + *(int *)mem[pc+2]];
      pc += 6;
      break;
    case 0x60:		// add %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value += reg_table[reg1].value;
      pc += 2;
      break;      
    case 0x61:		// sub %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value -= reg_table[reg1].value;
      pc += 2;
      break;   
    case 0x62:		// and %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value &= reg_table[reg1].value;
      pc += 2;
      break;   
    case 0x63:		// or %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value |= reg_table[reg1].value;
      pc += 2;
      break; 
    case 0x64:		// not %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value = !reg_table[reg1].value;
      pc += 2;
      break;  
    case 0x65:		// xor %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value ^= reg_table[reg1].value;
      pc += 2;
      break;   
    case 0x66:		// shl %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value >>= reg_table[reg1].value;
      pc += 2;
      break;   
    case 0x67:		// shr %reg1, %reg2
      reg1 = get_reg_id(mem[pc+1],1);
      reg2 = get_reg_id(mem[pc+1],2);
      reg_table[reg2].value <<= reg_table[reg1].value;
      pc += 2;
      break;   
    case 0x70:				// jmp Dest
      pc = (int) mem[pc+1];
      break;   
    case 0x71:				// je Dest
      if(reg_table[REG_FLAGS].value & 0x40)	// ZF == 1
	pc = (int) mem[pc+1];
      break;   
    case 0x72:					// jl Dest
      if(reg_table[REG_FLAGS].value ^ 0x880)		// (SF xor OF) == 1
	pc = (int) mem[pc+1];
      break;   
    case 0x73:					// jg Dest
      if(0 == ((reg_table[REG_FLAGS].value & 0x40) | (reg_table[REG_FLAGS].value ^ 0x880)))	// ((SF xor OF) or ZF) == 0
	pc = (int) mem[pc+1];
      break;   
    case 0x74:					// jne Dest
      if(0 == (reg_table[REG_FLAGS].value & 0x40))	// ZF == 0
	pc = (int) mem[pc+1];
      break;   
    case 0x75:					// jle Dest
      if((reg_table[REG_FLAGS].value & 0x40) | (reg_table[REG_FLAGS].value ^ 0x880))	// ((SF xor OF) or ZF) == 1
	pc = (int) mem[pc+1];
      break;   
    case 0x76:					// jge Dest
      if(0 == (reg_table[REG_FLAGS].value ^ 0x880))	// (SF xor OF) == 0
	pc = (int) mem[pc+1];
      break;   
    case 0x80:		// call Dest
      pc += 5;
      *(int *) mem[reg_table[REG_SP].value - 4] = pc;
      reg_table[REG_SP].value -= 4;
      pc = (int) mem[pc+1];
      break;   
    case 0x90:		// ret
      pc = (int) mem[reg_table[REG_SP].value];
      reg_table[REG_SP].value += 4;
      break;   
    case 0xa0:		// push %reg
      reg1 = get_reg_id(mem[pc+1], 1);
      *(int *) mem[reg_table[REG_SP].value - 4] = reg_table[reg1].value;
      reg_table[REG_SP].value -= 4;
      pc += 2;
      break;   
    case 0xb0:		// pop %reg
      reg1 = get_reg_id(mem[pc+1], 1);
      reg_table[reg1].value = (int) mem[reg_table[REG_SP].value];
      reg_table[REG_SP].value += 4;
      pc += 2;
      break;   
    case 0xc0:		// in imme, %reg
      /* not implement */
      pc += 3;
      break;   
    case 0xd0:		// out %reg, imme
      if(1 == mem[pc+2]){
        reg1 = get_reg_id(mem[pc+1],1);
        printf("%c", (char) reg_table[reg1].value);
      }
      pc += 3;
      break;
    default:
      sprintf(mem_content,"%x", mem[pc]);
      printf("err: pc=%d mem=%s\n",pc, mem_content);
      pc++;
    }
  }
}

int main(int argc, char **argv){
  char *bi_fname;
  FILE *bi_file;
  struct stat statbuff;		// used to get file length

  mem = malloc(4096);
  if(NULL == mem){
    printf("mem alloc err!\n");
    exit(0);
  }

  /* open binary file */
  bi_fname = argv[1];
  bi_file = fopen(bi_fname, "rb+");
  if(bi_file == NULL){
    printf("open binary file err: %s\n", bi_fname);
    exit(0);
  }

  /* load file to mem */
  stat(bi_fname, &statbuff);
  if(statbuff.st_size !=fread(mem, 1, statbuff.st_size, bi_file))
    printf("load file err\n");

  /* run from pc=0 until HALT or cycle<20 */
  cpu(0);

  free(mem);
  fclose(bi_file);
}
